package rayTracer;

import java.util.ArrayList;
import java.util.Collections;

import javax.vecmath.Point3d;
import static java.lang.Math.*;
import static rayTracer.ApplicationInterface.*;

public class AABB {
	public Point3d min;
	public Point3d max;
	
	public AABB( Point3d min, Point3d max ){
		this.min = min;
		this.max = max;		
	}
	
	public AABB( Point3d[] pts ){		
		ArrayList<Double>xList = new ArrayList<Double>();
		ArrayList<Double>yList = new ArrayList<Double>();
		ArrayList<Double>zList = new ArrayList<Double>();		
		for( Point3d p : pts ){
			xList.add(p.x);
			yList.add(p.y);
			zList.add(p.z);
		}
		Collections.sort(xList);
		Collections.sort(yList);
		Collections.sort(zList);
		int len = xList.size();
		min = new Point3d(xList.get(0), yList.get(0), zList.get(0));
		max = new Point3d(xList.get(len-1), yList.get(len-1), zList.get(len-1));		
	}
	
	public boolean overlapsWith( AABB bb ){
				
		
		//No hay interseccion si estan separados en alguno de los ejes
		if ( max.x <  bb.min.x || min.x > bb.max.x ) 
			return false;
		if ( max.y < bb.min.y || min.y > bb.max.y ) 
			return false;
		if ( max.z < bb.min.z || min.z > bb.max.z ) 
			return false;
		//Se superponen en todos los ejes, entonces los BB se superponen
		//return true;
		
		
		/*
//		No hay interseccion si estan separados en alguno de los ejes
		if ( max.x <=  bb.min.x || min.x >= bb.max.x || max.y <= bb.min.y || 
				min.y >= bb.max.y || max.z <= bb.min.z || min.z >= bb.max.z ){
			STATES minStatus = pointRelativePositionToAABB(bb.min);
			STATES maxStatus = pointRelativePositionToAABB(bb.max);
			
			if( ( minStatus == STATES.OUT && maxStatus != STATES.IN ) || 
				( maxStatus == STATES.OUT && minStatus != STATES.IN ) )
				return false;
			return true;
		}
		*/
		return true;	
		
		
		/*
		STATES minStatus = pointRelativePositionToAABB(bb.min);
		STATES maxStatus = pointRelativePositionToAABB(bb.max);
		
		if( ( minStatus == STATES.OUT && maxStatus != STATES.IN ) || 
			( maxStatus == STATES.OUT && minStatus != STATES.IN ) )
			return false;
		return true;
		*/
		
	}
	
	@SuppressWarnings("unused")
	private STATES pointRelativePositionToAABB( Point3d p ){		
		int aX = Double.compare(min.x, p.x);
		int bX = Double.compare(p.x, max.x);
		
		int aY = Double.compare(min.y, p.y);
		int bY = Double.compare(p.y, max.y);
		
		int aZ = Double.compare(min.z, p.z);
		int bZ = Double.compare(p.z, max.z);
		
		if( aX > 0 || bX > 0 || aY > 0 || bY > 0 || aZ > 0 || bZ > 0 )
			return STATES.OUT;
		if( aX == 0 || bX == 0 || aY == 0 || bY == 0 || aZ == 0 || bZ == 0 )
			return STATES.EDGE;
		return STATES.IN;		
	}
	
	public boolean containedIn( AABB bb ){
		 return bb.min.x <= min.x && bb.min.y <= min.y && bb.min.z <= min.z  &&
				bb.max.x >= max.x && bb.max.y <= max.y && bb.max.z <= max.z ;	
	}
	
	//Intersecta con el rayo R(t) = p + t*d 
	public double intersectRayMinDistance( Ray ray){		
		double[] p = new double[3];
		double[] d = new double[3];
		double[] min = new double[3];
		double[] max = new double[3];		
		ray.origin.get(p);
		ray.vecDir.get(d);
		this.min.get(min);
		this.max.get(max);
		
		double tmin = 0.0d;
		double tmax = Double.MAX_VALUE; 
		//Para cada Volumen director generado por los planos del BB
		for (int i = 0; i < 3; i++) {			
			if ( abs(d[i]) < 0.00001 ) {
				//El rayo es paralelo al volumen director. Si el punto no esta
				//dentro del volumen, entonces no hay interseccion
				if (p[i] < min[i] || p[i] > max[i])
					return Double.NaN;
			} 
			else{
				/* Calcular el valor t que hace tocar el rayo con el plano mas
				   cercano y con el plano mas lejano del volumen director */
				double dimensionFactor = 1.0d / d[i];
				double t1 = (min[i] - p[i]) * dimensionFactor;				 
				double t2 = (max[i] - p[i]) * dimensionFactor;
				//Fuerzo a que t1 corresponda con el plano mas cercano
				if (t1 > t2){
					double aux= 0;
					aux = t1;
					t1 = t2;
					t2 = aux;
				}					
				//Comparo los valores t con los valores limites de interseccion
				if (t1 > tmin) 
					tmin = t1;
				if (t2 < tmax) 
					tmax = t2;
				//No hay colision cuando los limites dan un volumen nulo o negativo 
				if (tmin > tmax) 
					return Double.NaN;
			}
		}
		return tmin;
		
	}
	
	public boolean intersectRay( Ray ray){		
		return !Double.isNaN(intersectRayMinDistance(ray));
	}
	
	public boolean intersectRay( Ray ray, Point3d q){
		double tmin = intersectRayMinDistance(ray);
	
		if ( Double.isNaN(tmin) )
			return false;
		
		//Calculo el punto con el tmin
		q.scaleAdd(tmin, ray.vecDir, ray.origin);
		return true;	
	}
	
	public String toString(){
		StringBuffer ans = new StringBuffer("");
		ans.append( "BB => min = " +  min + "\t max = " + max );
		return ans.toString();
	}
}
